<?php
/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * This class can be used to automatically create a wrapper class for Google API end points
 * Using wrapper classes allows the IDE to auto complete function names and offer suggestions for parameters
 * making the API simpler to use.
 *
 * The generated wrapper class calls the api client's overloader function (__call) directly, so the behaviour
 * is identical to calling the long for functions directly
 *
 * @author Chris Chabot <chabotc@google.com>
 *
 */
class apiGenerator extends apiClient {

  private $discoveryUrl;
  private $discoveryVersion = 'v0.2beta1';
  private $serviceName;
  private $version;

  public function __construct($serviceName, $version) {
    global $apiConfig;
    parent::__construct();
    $this->serviceName = $serviceName;
    $this->version = $version;
    $this->discoveryUrl = $apiConfig['basePath'] . '/discovery/' . self::discoveryVersion . '/describe/' . urlencode($serviceName) . '/' . urlencode($version);
  }

  public function generate() {
    global $apiConfig;
    $discoveryDocument = $this->getService();
    if (!isset($discoveryDocument['version']) || !isset($discoveryDocument['restBasePath']) || !isset($discoveryDocument['rpcPath'])) {
      throw new apiServiceException("Invalid discovery document");
    }

    // Construct the class code in the correct order
    $ret = ''
          . $this->generateLicenseHeader()
          . $this->generateCommentHeader()
          . $this->generateMeta()
          . $this->generateApiServiceClass();
    return $ret;
  }

  private function getService() {
    $request = $this->io->makeRequest(new apiHttpRequest($this->discoveryUrl));
    if ($request->getResponseHttpCode() != 200) {
      throw new apiException("Could not fetch discovery document for {$this->serviceName} at {$this->discoveryUrl}, http code: " . $request->getResponseHttpCode() . ", response body: " . $request->getResponseBody());
    }
    $discoveryResponse = $request->getResponseBody();
    if (($discoveryDocument = json_decode($discoveryResponse, true)) == null) {
      throw new apiException("Invalid discovery document json");
    }
    return $discoveryDocument;
  }

  private function miscFunctions() {
    return
"  /**
   * @return the \$io
   */
  public function getIo() {
    return \$this->io;
  }

  /**
   * @return the \$version
   */
  public function getVersion() {
    return \$this->version;
  }

  /**
   * @return the \$restBasePath
   */
  public function getRestBasePath() {
    return \$this->restBasePath;
  }

  /**
   * @return the \$rpcPath
   */
  public function getRpcPath() {
    return \$this->rpcPath;
  }";
  }

  private function generateLicenseHeader() {
    return
"/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the \"License\");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an \"AS IS\" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */";
  }

  private function generateCommentHeader() {
    return
"\n\n/**\n".
" * The " . ucfirst($this->serviceName) . " service implementation\n".
" *\n".
" * Generated by http://code.google.com/p/google-api-php-client/\n".
" * Generated from: {$this->discoveryUrl}\n" .
" **/\n";
  }

  private function generateMeta() {
    $discoveryDocument = $this->getService();
    $classes = '';
    $schemaClasses = $discoveryDocument['schemas'];
    while(sizeof($schemaClasses) > 0) {
      $metaClassConfig = array_shift($schemaClasses);
      $class = '';
      if ('object' == $metaClassConfig['type']) {
        $class .=  "class {$metaClassConfig['id']} {\n";
        foreach ($metaClassConfig['properties'] as $property => $propertyConfig) {
          if ('object' == $propertyConfig['type'] && isset($propertyConfig['properties'])) {
            if (!isset($propertyConfig['id'])) {
              $propertyConfig['id'] = $metaClassConfig['id'] . ucwords($property);
            }
            array_unshift($schemaClasses, $propertyConfig);
          }
          $propertyVal = is_string($propertyConfig) ? "' = $propertyConfig'" : '';
          $class .=  "  public \$$property$propertyVal;\n";
        }
        $classes .= $class . "}\n\n";
      }
    }
    return $classes;
  }

  private function generateApiServiceClass() {
    $discoveryDocument = $this->getService();
    $discoveryResources = $discoveryDocument['resources'];
    $vars = "  // Variables that the apiServiceResource implementation depends on\n" .
            "  private \$serviceName = '{$this->serviceName}';\n".
            "  private \$version = '{$this->version}';\n".
            "  private \$restBasePath = '{$discoveryDocument['restBasePath']}';\n".
            "  private \$rpcPath = '{$discoveryDocument['rpcPath']}';\n".

            "  private \$io;\n".
            "  // apiServiceResource's that are used internally\n";
    $constructor = "  /**\n" .
                   "   * Constructs the internal service representations and does the auto-magic configuration required to drive them\n" .
                   "   */\n" .
                   "  public function __construct(apiClient \$apiClient) {\n" .
                   "    \$apiClient->addService('" . $this->serviceName . "', '" . $this->version . "');\n".
                   "    \$this->io = \$apiClient->getIo();\n";
    $functions = '';
    foreach ($discoveryResources as $resourceName => $resourceConfig) {
      $vars .= "  private \${$resourceName};\n";
      $constructor .= "    \$this->{$resourceName} = new apiServiceResource(\$this, \$this->serviceName, '$resourceName', json_decode('". str_replace("'", "\\'", json_encode($resourceConfig)). "', true));\n";
      if (isset($resourceConfig['methods'])) {
        foreach ($resourceConfig['methods'] as $methodName => $methodConfig) {
          $requiredParams = $optionalParams = array();
          if (isset($methodConfig['parameters'])) {
            foreach ($methodConfig['parameters'] as $paramName => $paramConfig) {
              if ($paramName == 'alt') {
                // the library depends on everything being json so we can't change the format (alt) parameter
                continue;
              }
              //TODO check for the param's description here so we can add it to the docs below (line 107)
              $paramName = str_replace('-', '_', $paramName);
              if (isset($paramConfig['required']) && $paramConfig['required']) {
                $requiredParams[] = "\$$paramName";
              } else {
                $optionalParams[] = "\$$paramName = null";
              }
              $paramDescriptions[$paramName] = '';
              if (isset($paramConfig['type'])) {
                $paramDescriptions[$paramName] .= ' ' . $paramConfig['type'] . ' ';
              }
              if (isset($paramConfig['description'])) {
                $paramDescriptions[$paramName] .= $paramConfig['description'];
              }
              if (isset($paramConfig['enumDescriptions'])) {
                $enumDescription = ", valid values are:\n";
                foreach ($paramConfig['enumDescriptions'] as $key => $val) {
                  $enumDescription .= '   *                 ' . $paramConfig['enum'][$key] . ' : ' . $val;
                  if ($key != count($paramConfig['enumDescriptions']) -1) {
                    $enumDescription .= "\n";
                  }
                }
                $paramDescriptions[$paramName] .= $enumDescription;
              }
            }
          }
          if (strtoupper($methodConfig['httpMethod']) == 'POST' || strtoupper($methodConfig['httpMethod']) == 'PUT') {
            $requiredParams[] = '$postBody';
          }
          $params = array_merge($requiredParams, $optionalParams);

          $description = isset($methodConfig['description']) ? $methodConfig['description'] : "Implementation of the {$methodConfig['rpcMethod']} method.";
          $functions .= "  /**\n".
                "   * $description\n   *\n";
          // TODO add link back to the docs:"   * See: http://code.google.com/apis/buzz/v1/using_rest.html#{$methodConfig['rpcMethod']}\n   *\n";
          $paramToArrayMapping = array();
          foreach ($params as $param) {
            $paramName = str_replace('$', '', str_replace(' = null', '', $param));
            $functions .= "   * @param \$$paramName ";
            if (isset($paramDescriptions[$paramName])) {
              $functions .=  ' ' . $paramDescriptions[$paramName];
            }
            $functions .= "\n";
            $paramToArrayMapping[] = "'". str_replace('_', '-', $paramName) . "' => \${$paramName}";
          }
          $functions .= "   */\n";
          $functions .= "  public function {$methodName}" . ucfirst($resourceName) . "(".
                        implode(",\n        ", $params).
                        ") {\n".
                        "    return \$this->{$resourceName}->__call('$methodName', array(array(" . implode(",\n        ", $paramToArrayMapping) . ')));' . "\n" .
                        "  }\n\n";
        }
      }
    }
    $constructor .= "  }\n\n";

    return "class api" . ucfirst($this->serviceName) . "Service {\n\n"
          . $vars . "\n"
          . $constructor
          . $functions
          . $this->miscFunctions()
          . "\n}\n\n"; // End of class
  }
}
